#include "common.h"
#include <malloc.h>
#include <Windows.h>

value loader=NULL;
value pyProto=NULL;
DEFINE_KIND(k_pyobject);

value ConvPy2Neko(PyObject* v) {
if(Py_None==v) {
	return val_null;
} else if(PyInt_Check(v)) {
	return alloc_best_int(PyInt_AsLong(v));
} else if(PyFloat_Check(v)) {
	return alloc_float(PyFloat_AsDouble(v));
} else if(PyBool_Check(v)) {
	return alloc_bool(Py_True==v);
} else if(PyString_Check(v)) {
	return alloc_string(PyString_AsString(v));
} else if(PyTuple_Check(v)) {
	int i, size = PyTuple_Size(v);
	value result = alloc_array(size);
	value *ptr = val_array_ptr(result);
	for(i=0; i<size; ++i) ptr[i] = ConvPy2Neko(PyTuple_GetItem(v, i));
	return result;
} else if(NekoObject_Check(v)) {
	return ((NekoObject*)v)->nekoPtr;
} else {
	value obj = alloc_object(pyProto);
	alloc_field(obj, val_id("pyPtr"), alloc_abstract(k_pyobject, v));
	return obj;
	}
}

//------------------------------------------------------------------------------

PyObject *ConvNeko2Py(value v) {
switch( val_type(v) ) {
	case VAL_NULL: {
		Py_INCREF(Py_None);
		return Py_None;
				   }
	case VAL_INT: { return PyInt_FromLong(val_int(v)); }
	case VAL_FLOAT: { return PyFloat_FromDouble(val_float(v)); }
	case VAL_BOOL: { return val_bool(v)?Py_True:Py_False; }
	case VAL_STRING: { return PyString_FromString(val_string(v)); }
	case VAL_ABSTRACT: { if(val_is_kind(v, k_pyobject)) {
		PyObject* ret = (PyObject*)val_data(v);
		Py_INCREF(ret);
		return ret;
		}
					   }
	case VAL_OBJECT: if(val_is_object(v)) { 
			field f = val_id("pyPtr");
			value ptr = val_field(v, f);
			if(!val_is_null(ptr))return ConvNeko2Py(ptr);
					 }
	default: {
		NekoObject *obj = PyObject_New(NekoObject, &NekoObject_Type);
		if(obj) obj->nekoPtr = v;
		return (PyObject*) obj;
		}
/**/}
}

//------------------------------------------------------------------------------

static PyObject *NekoObject_getattr(NekoObject *obj, PyObject *attr) {
	return ConvNeko2Py(val_field(obj->nekoPtr, val_id(PyString_AsString(attr))));
} 

static int NekoObject_setattr(NekoObject *obj, PyObject *attr, PyObject *v) {
	alloc_field(obj->nekoPtr, val_id(PyString_AsString(attr)), ConvPy2Neko(v));
	return 0;
} 

static PyObject *NekoObject_str(NekoObject *obj) {
register buffer b;
value v = obj->nekoPtr;
if(val_is_object(v)) {
	field fstr = val_id("__string");
	if(val_field(v, fstr)!=val_null) return PyString_FromString(val_string(val_ocall0(v, fstr)));
	}
b = alloc_buffer("");
val_buffer(b, v);
return PyString_FromString(val_string(buffer_to_string(b)));
}

static PyObject *NekoObject_repr(NekoObject *obj) {
register value v = obj->nekoPtr;
     if(val_is_null(v))     return PyString_FromFormat("<Neko.Null at %p>", v);
else if(val_is_int(v))      return PyString_FromFormat("<Neko.Int at %p with value=%i>", v, val_int(v));
else if(val_is_float(v))    return PyString_FromFormat("<Neko.Float at %p with value=%f>", v, val_float(v));
else if(val_is_bool(v))     return PyString_FromFormat("<Neko.Bool at %p with value=%s>", v, val_bool(v)?"True":"False");
else if(val_is_array(v))    return PyString_FromFormat("<Neko.Array at %p with size=%i>", v, val_array_size(v));
else if(val_is_function(v)) return PyString_FromFormat("<Neko.Function at %p with nargs=%i>", v, val_fun_nargs(v));
else if(val_is_string(v))   return PyString_FromFormat("<Neko.String at %p with len=%i>", v);
else if(val_is_object(v))   return PyString_FromFormat("<Neko.Object at %p>", v);
else if(val_is_abstract(v)) return PyString_FromFormat("<Neko.Abstract at %p with kind=%x>", v, val_kind(v));
else return PyString_FromFormat("<Neko Unknown at %p>", v);
}

static PyObject *NekoObject_call(NekoObject *obj, PyObject *args) {
int size = PyTuple_Size(args);
value *neko_args = (value*)malloc(size*sizeof(value));
int i;
value loader;
value exc = NULL;
value ret;
for(i=0; i!=size; i++) neko_args[i] = ConvPy2Neko(PyTuple_GetItem(args, i));
loader = neko_default_loader(NULL, 0);
ret = val_callEx(loader, obj->nekoPtr, neko_args, size, &exc);
if( exc != NULL ) {
    buffer b = alloc_buffer(NULL);
    val_buffer(b,exc);
    PyErr_SetString(PyExc_RuntimeError, val_string(buffer_to_string(b)));
    return NULL;
}
free(neko_args);
return ConvNeko2Py(ret);
}

int NekoCompare(value a, value b) {
if(val_type(a)==val_type(b))
	switch( val_type(a) ) {
		case VAL_NULL: { return 0; }
		case VAL_INT: { 
			int ia = val_int(a), ib = val_int(b);
			return (ia < ib) ? -1 : (ia > ib) ? 1 : 0;  }
		case VAL_FLOAT: { 
			double fa = val_float(a), fb = val_float(b);
			return (fa < fb) ? -1 : (fa > fb) ? 1 : 0;  }
		case VAL_BOOL: { 
			bool fa = val_bool(a), fb = val_bool(b);
			return fb && !fa ? -1 : fa && !fb ? 1 : 0;  }
		case VAL_STRING: { 
			return strcmp(val_string(a), val_string(b)); }
	}
if(!val_is_object(a)) {
	PyErr_SetString(PyExc_TypeError, "Incomparable Neko.Objects");
	return -1;
} else {
	field func = val_id("__compare");
	if(val_field(a, func)!=val_null) return val_int(val_ocall1(a, func, b));
	}
PyErr_SetString(PyExc_AttributeError, "No comparison operator");
return -1;
}

static int neko_compare(NekoObject *a, PyObject *b) {
value v_a, v_b;
if(!NekoObject_Check(b)) {
	PyErr_SetString(PyExc_TypeError, "Incomparable types");
	return -1;
}
v_a = a->nekoPtr;
v_b = ((NekoObject*)b)->nekoPtr;
return NekoCompare(v_a, v_b);
}
 
//------------------------------------------------------------------------------

static PyObject *neko_add(NekoObject *a, NekoObject *b) {
register value v = a->nekoPtr, v2 = b->nekoPtr;
if(val_is_object(v)) {
	field func = val_id("__add");
	if(val_field(v, func)!=val_null) return ConvNeko2Py(val_ocall1(v, func, v2));
	}
if(val_is_object(v2)) {
	field func = val_id("__radd");
	if(val_field(v2, func)!=val_null) return ConvNeko2Py(val_ocall1(v2, func, v));
	}
return NULL;
}

static PyObject *neko_sub(NekoObject *a, NekoObject *b) {
register value v = a->nekoPtr, v2 = b->nekoPtr;
if(val_is_object(v)) {
	field func = val_id("__sub");
	if(val_field(v, func)!=val_null) return ConvNeko2Py(val_ocall1(v, func, v2));
	}
if(val_is_object(v2)) {
	field func = val_id("__rsub");
	if(val_field(v2, func)!=val_null) return ConvNeko2Py(val_ocall1(v2, func, v));
	}
return NULL;
}

static PyObject *neko_mul(NekoObject *a, NekoObject *b) {
register value v = a->nekoPtr, v2 = b->nekoPtr;
if(val_is_object(v)) {
	field func = val_id("__mul");
	if(val_field(v, func)!=val_null) return ConvNeko2Py(val_ocall1(v, func, v2));
	}
if(val_is_object(v2)) {
	field func = val_id("__rmul");
	if(val_field(v2, func)!=val_null) return ConvNeko2Py(val_ocall1(v2, func, v));
	}
return NULL;
}

static PyObject *neko_div(NekoObject *a, NekoObject *b) {
register value v = a->nekoPtr, v2 = b->nekoPtr;
if(val_is_object(v)) {
	field func = val_id("__div");
	if(val_field(v, func)!=val_null) return ConvNeko2Py(val_ocall1(v, func, v2));
	}
if(val_is_object(v2)) {
	field func = val_id("__rdiv");
	if(val_field(v2, func)!=val_null) return ConvNeko2Py(val_ocall1(v2, func, v));
	}
return NULL;
}

static PyObject *neko_mod(NekoObject *a, NekoObject *b) {
register value v = a->nekoPtr, v2 = b->nekoPtr;
if(val_is_object(v)) {
	field func = val_id("__mod");
	if(val_field(v, func)!=val_null) return ConvNeko2Py(val_ocall1(v, func, v2));
	}
if(val_is_object(v2)) {
	field func = val_id("__rmod");
	if(val_field(v2, func)!=val_null) return ConvNeko2Py(val_ocall1(v2, func, v));
	}
return NULL;
}

static PyNumberMethods neko_as_number = {
	(binaryfunc)neko_add,			/*nb_add*/
	(binaryfunc)neko_sub,			/*nb_subtract*/
	(binaryfunc)neko_mul,			/*nb_multiply*/
	(binaryfunc)neko_div,			/*nb_divide*/
	(binaryfunc)neko_mod,			/*nb_remainder*/
};

static int nekolength(NekoObject *a) { 
register value v=a->nekoPtr;
if(!val_is_array(v)) {
	PyErr_SetString(PyExc_TypeError, "Neko.Array expected for length calculation"); 
	return -1;
} else return val_array_size(v);
}

static PyObject *nekoconcat(register NekoObject *a, register PyObject *b) {
value v_a, v_b, v_c, *a_ptr, *b_ptr, *c_ptr;
int a_size, b_size, i;
	if (!NekoObject_Check(b)) { PyErr_Format(PyExc_TypeError,
       		    "can't only concatenate Neko.Array (not \"%.200s\") to Neko.Array",
			    b->ob_type->tp_name);
	return NULL;
}
v_a =a->nekoPtr;
v_b =((NekoObject *)b)->nekoPtr;
if(!val_is_array(v_a) || !val_is_array(v_b)) {
	PyErr_SetString(PyExc_TypeError, "Neko.Array expected for length concatenation");
	return NULL;
}
a_size = val_array_size(v_a);
b_size = val_array_size(v_b);
v_c = alloc_array(a_size + b_size);
a_ptr=val_array_ptr(v_a);
b_ptr=val_array_ptr(v_b);
c_ptr=val_array_ptr(v_c);
for(i=0; i<a_size; ++i) *(c_ptr++)=*(a_ptr++);
for(i=0; i<b_size; ++i) *(c_ptr++)=*(b_ptr++);
return ConvNeko2Py(v_c);
} 

static PyObject *nekorepeat(NekoObject *a, int n) {
value v_a =a->nekoPtr, v_b, *a_ptr, *b_ptr;
int a_size = val_array_size(v_a), i, j;
if(!val_is_array(v_a)) {
	PyErr_SetString(PyExc_TypeError, "Neko.Array expected for repeatition");
	return NULL;
}
v_b = alloc_array(a_size * n);
a_ptr=val_array_ptr(v_a);
b_ptr=val_array_ptr(v_b);
for(j=0; j<n; ++j) for(i=0; i<a_size; ++i) *(b_ptr++)=a_ptr[i];
return ConvNeko2Py(v_b);
}

static PyObject *nekoitem(register NekoObject *a, register int i) {
value v_a = a->nekoPtr;
int a_size = val_array_size(v_a);
if(val_is_object(v_a)) {
	field func = val_id("__get");
	if(val_field(v_a, func)!=val_null) return ConvNeko2Py(val_ocall1(v_a, func, alloc_int(i)));
	}
if(!val_is_array(v_a)) {
    PyErr_SetString(PyExc_TypeError, "Neko.Array expected for indexing");
	return NULL;
	}
if(i < 0 || i >= a_size) {
	PyErr_SetString(PyExc_IndexError, "Neko.Array index out of range");
	return NULL;
	}
return ConvNeko2Py(val_array_ptr(v_a)[i]);
} 

static PyObject *nekoslice(register NekoObject *a, register int ilow, register int ihigh) {
value v_a =a->nekoPtr, v_b, *a_ptr, *b_ptr;
int i, len, a_size = val_array_size(v_a);
if(!val_is_array(v_a)) {
	PyErr_SetString(PyExc_TypeError, "Neko.Array expected for slicing");
	return NULL;
}
if (ilow < 0) ilow = 0;
if (ihigh > a_size)	ihigh = a_size;
if (ihigh < ilow) ihigh = ilow;
if (ilow == 0 && ihigh == a_size) {
	Py_INCREF(a);
	return (PyObject *)a;
	}
len = ihigh - ilow;
v_b = alloc_array(len);
a_ptr=val_array_ptr(v_a) + ilow;
b_ptr=val_array_ptr(v_b);
for(i=0; i<len; ++i) *(b_ptr++)=*(a_ptr++);
return ConvNeko2Py(v_b);
}

static int neko_ass_item(NekoObject *a, int i, PyObject *b) {
value v = a->nekoPtr;
int a_size = val_array_size(v);
if(val_is_object(v)) {
	field func = val_id("__set");
	if(val_field(v, func)!=val_null) {
		ConvNeko2Py(val_ocall2(v, func, alloc_int(i), ConvPy2Neko(b)));
		return 0;
		}
	}
if(!val_is_array(v)) {
    PyErr_SetString(PyExc_TypeError, "Neko.Array expected for item assignment");
	return -1;
	}
if(i < 0 || i >= a_size) {
	PyErr_SetString(PyExc_IndexError, "Neko.Array assignment index out of range");
	return -1;
	}
val_array_ptr(v)[i]=ConvPy2Neko(b);
return 0;
}
 
static PySequenceMethods neko_as_sequence = {
	(inquiry)nekolength,			/* sq_length */
	(binaryfunc)nekoconcat,		    /* sq_concat */
	(intargfunc)nekorepeat,			/* sq_repeat */
	(intargfunc)nekoitem,			/* sq_item */
	(intintargfunc)nekoslice,		/* sq_slice */
	(intobjargproc)neko_ass_item,	/* sq_ass_item */
0,//	0,								/* sq_ass_slice */
0,//	(objobjproc)tuplecontains,		/* sq_contains */
};

PyTypeObject NekoObject_Type = {
		PyObject_HEAD_INIT(NULL)
		0,								/*ob_size*/
		"Neko.Type",					/*tp_name*/
		sizeof(NekoObject),				/*tp_basicsize*/
		0,								/*tp_itemsize*/
		&PyObject_Del,					/*tp_dealloc*/
		0,								/*tp_print*/
		0,								/*tp_getattr*/
		0,								/*tp_setattr*/
		(cmpfunc)neko_compare,			/*tp_compare*/
		(reprfunc)NekoObject_repr,		/*tp_repr*/
		&neko_as_number,				/*tp_as_number*/
		&neko_as_sequence,				/*tp_as_sequence*/
		0,								/*tp_as_mapping*/
		0,								/*tp_hash*/
		(ternaryfunc)NekoObject_call,	/*tp_call*/
		(reprfunc)NekoObject_str,		/*tp_str*/
		(getattrofunc)NekoObject_getattr,/*tp_getattro*/
		(setattrofunc)NekoObject_setattr,/*tp_setattro*/
		0,								/*tp_as_buffer*/
		Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
		0,           					/*tp_doc*/
		0,								/*tp_traverse*/
		0,								/*tp_clear*/
		0,								/*tp_richcompare*/
		0,								/*tp_weaklistoffset*/
		0,								/*tp_iter*/
		0,								/*tp_iternext*/
		0,       						/*tp_methods*/
		0,       						/*tp_members*/
		0,								/*tp_getset*/
		0,								/*tp_base*/
		0,								/*tp_dict*/
		0,								/*tp_descr_get*/
		0,								/*tp_descr_set*/
		0,								/*tp_dictoffset*/
		0,								/*tp_init*/
		PyType_GenericAlloc,			/*tp_alloc*/
		PyType_GenericNew,				/*tp_new*/
		_PyObject_Del,       			/*tp_free*/
		0,								/*tp_is_gc*/
};

BOOL APIENTRY DllMain( HANDLE hModule, 
					  DWORD  ul_reason_for_call, 
					  LPVOID lpReserved
					  )
{
	return TRUE;
}
